﻿#pragma once

#include <string>
#include <list>
#include <sstream>
#include <typeinfo>
#include <thread>
#include <chrono>
#include <ctime>
#include <iostream>
#include <initializer_list>
#include "fixture.hh"
#include "testcase.hh"
#include "color.hh"
#include "../../src/util/string_util.hh"

namespace k {
namespace test {

// 单元测试框架
class Unittest {
    using FixtureList = std::list<Fixture*>;
    FixtureList fixtureList_; // 用例集合列表
    static Unittest* unittest_; // 单件
    bool debugBreak_{ false };

public:
    // 取得框架指针
    inline static Unittest* unittest() {
        if (!unittest_) {
            unittest_ = new Unittest();
        }
        return unittest_;
    }

public:
    // 构造
    Unittest();
    // 析构
    ~Unittest();
    // 添加用例集合
    bool addFixture(Fixture* fixture);
    // 取得用例集合
    // @param name 名称
    Fixture* getFixture(const std::string& name);
    // 禁用集合
    // @param name 名称
    void disableFixture(const std::string& name);
    // 允许运行集合
    // @param name 名称
    void enableFixture(const std::string& name);
    // 运行所有集合
    void runAllFixture();
    // 运行所有集合，某几个除外
    void runAllFixtureExcept(std::initializer_list<std::string> list);
    // 运行集合
    void runFixture(const std::string& name, std::size_t count = 1);
    // 等待用户按键
    void waitKeyPress();
    // 获取所有用例集合列表
    const FixtureList& getAllFixture();
    // 设置DEBUG BREAK
    void setDebugBreak();
    // 检测DEBUG BREAK
    bool isDebugBreak();
};

}
}

#define UnittestRef (*k::test::Unittest::unittest())

#if defined(WIN32) && defined(_DEBUG) 
inline void triggerDebugBreak() {
    if (UnittestRef.isDebugBreak()) {
        DebugBreak();
    }
}
#else
inline void triggerDebugBreak() {
    return;
}
#endif

#define FIXTURE_BEGIN(name) \
    namespace _namespace_##name { \
    static k::test::Fixture* _fixture = new k::test::Fixture(std::string(#name)); \
    static bool _fixture_add = UnittestRef.addFixture(_fixture);

#define FIXTURE_END(name) }

#define SETUP(method) static bool _fixture_setup_add = _fixture->onSetup(method);
#define TEARDOWN(method) static bool _fixture_teardown_add =_fixture->onTearDown(method);

#define CASE(name) \
    class _case_##name : public k::test::Testcase {\
        bool stdexcept_; \
        bool except_; \
    public: \
        _case_##name(const std::string& name) : k::test::Testcase(#name), stdexcept_(false), except_(false) { }\
        virtual ~_case_##name() = default; \
        virtual void run() override; \
    }; \
    static bool _testcase_##name##_add = _fixture->addTestcase(new _case_##name(#name)); \
    void _case_##name::run()

#define ASSERT_TRUE(expr) \
    if (!(expr)) { \
        std::stringstream error; \
        error << __FILE__ << ":" << __func__ << ":" << __LINE__ << ":" << std::endl << "    > " << #expr; \
        setError(error.str()); \
        triggerDebugBreak(); \
    }

#define ASSERT_FALSE(expr) ASSERT_TRUE(!(expr))

#define ASSERT_EQUAL(a, b) ASSERT_TRUE((a) == (b))

#define ASSERT_NEQUAL(a, b) ASSERT_TRUE((a) != (b))

#define ASSERT_NULL(expr) ASSERT_TRUE((a) == nullptr)

#define ASSERT_STDEXCEPT(expr) \
    stdexcept_ = false; \
    try { \
        expr; \
    } catch (std::exception& e) { \
        std::cout << "Exception type [" << kratos::util::demangle(typeid(e).name()) << "]\n" << e.what() << std::endl; \
        stdexcept_ = true; \
    } \
    ASSERT_TRUE(stdexcept_); \
    stdexcept_ = false;

#define ASSERT_EXCEPT(expr) \
    except_ = false; \
    try { \
        expr; \
    } catch (std::exception& e) { \
        std::cout << "Exception type [" << kratos::util::demangle(typeid(e).name()) << "]\n" << e.what() << std::endl; \
        except_ = true; \
    } \
    ASSERT_TRUE(except_); \
    except_ = false;

#define ASSERT_NOEXCEPT(expr) \
    except_ = false; \
    try { \
        expr; \
    } catch (...) { \
        except_ = true; \
    } \
    ASSERT_TRUE(!except_); \
    except_ = false;

inline bool check_sleep(bool& b, std::time_t maxMs) {
    std::time_t c = 0;
    while (!b) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        c += 100;
        if (c > maxMs) {
            return b;
        }
    }
    return b;
}

inline bool check_sleep(bool& p1, bool& p2, std::time_t maxMs) {
    std::time_t c = 0;
    while (!p1 || !p2) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        c += 100;
        if (c > maxMs) {
            return p1 && p2;
        }
    }
    return p1 && p2;
}

inline bool check_sleep(bool& p1, bool& p2, bool& p3, std::time_t maxMs) {
    std::time_t c = 0;
    while (!p1 || !p2 || !p3) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        c += 100;
        if (c > maxMs) {
            return p1 && p2;
        }
    }
    return p1 && p2 && p3;
}

